/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.ui.palette;

import com.architexa.org.eclipse.draw2d.FigureCanvas;
import com.architexa.org.eclipse.draw2d.IFigure;
import com.architexa.org.eclipse.gef.EditDomain;
import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.GraphicalEditPart;
import com.architexa.org.eclipse.gef.editparts.SimpleRootEditPart;
import com.architexa.org.eclipse.gef.internal.ui.palette.PaletteSelectionTool;
import com.architexa.org.eclipse.gef.internal.ui.palette.editparts.DrawerEditPart;
import com.architexa.org.eclipse.gef.internal.ui.palette.editparts.PaletteEditPart;
import com.architexa.org.eclipse.gef.internal.ui.palette.editparts.PaletteStackEditPart;
import com.architexa.org.eclipse.gef.internal.ui.palette.editparts.ToolEntryEditPart;
import com.architexa.org.eclipse.gef.palette.PaletteDrawer;
import com.architexa.org.eclipse.gef.palette.PaletteEntry;
import com.architexa.org.eclipse.gef.palette.PaletteListener;
import com.architexa.org.eclipse.gef.palette.PaletteRoot;
import com.architexa.org.eclipse.gef.palette.PaletteStack;
import com.architexa.org.eclipse.gef.palette.ToolEntry;
import com.architexa.org.eclipse.gef.ui.palette.customize.PaletteCustomizerDialog;
import com.architexa.org.eclipse.gef.ui.parts.PaletteViewerKeyHandler;
import com.architexa.org.eclipse.gef.ui.parts.ScrollingGraphicalViewer;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Display;

import org.eclipse.ui.IMemento;



/**
 * Graphical viewer for the GEF palette.
 * 
 * @author Randy Hudson
 * @author Pratik Shah
 */
public class PaletteViewer
	extends ScrollingGraphicalViewer
{

private class PreferenceListener
	implements PropertyChangeListener
{
	/**
	 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent evt) {
		String property = evt.getPropertyName();
		EditPart root = getRootEditPart().getContents();
		if (property.equals(PaletteViewerPreferences.PREFERENCE_FONT)) {
			updateFont();
			refreshAllEditParts(root);
		} else if (property.equals(PaletteViewerPreferences.PREFERENCE_LAYOUT) 
				|| property.equals(PaletteViewerPreferences.PREFERENCE_AUTO_COLLAPSE)
				|| property.equals(DefaultPaletteViewerPreferences
					.convertLayoutToPreferenceName(getPaletteViewerPreferences()
					.getLayoutSetting()))) {
			refreshAllEditParts(root);
		}
	}
	private void refreshAllEditParts(EditPart part) {
		part.refresh();
		List children = part.getChildren();
		for (Iterator iter = children.iterator(); iter.hasNext();) {
			EditPart child = (EditPart) iter.next();
			refreshAllEditParts(child);
		}
	}
}

private static final PaletteViewerPreferences PREFERENCE_STORE = 
				new DefaultPaletteViewerPreferences();
private ToolEntry activeEntry = null;
private PaletteCustomizer customizer = null;
private PaletteCustomizerDialog customizerDialog = null;
private boolean globalScrollbar = false;
private List paletteListeners = new ArrayList();
private PaletteRoot paletteRoot = null;
private PreferenceListener prefListener = new PreferenceListener();
private PaletteViewerPreferences prefs = PREFERENCE_STORE;
private Font font = null;

/**
 * Constructor
 */
public PaletteViewer() {
	EditDomain domain = new EditDomain();
	domain.setDefaultTool(new PaletteSelectionTool());
	domain.loadDefaultTool();
	setEditDomain(domain);
	setKeyHandler(new PaletteViewerKeyHandler(this));
	setEditPartFactory(new PaletteEditPartFactory());
}

/**
 * Adds the given PaletteListener as the one to be notified when the active tool on the
 * palette changes.
 * 
 * @param	paletteListener		The listener that needs to be notified of active tool 
 * 								changes on the palette
 */
public void addPaletteListener(PaletteListener paletteListener) {
	if (paletteListeners != null)
		paletteListeners.add(paletteListener);
}

/**
 * @see com.architexa.org.eclipse.gef.ui.parts.GraphicalViewerImpl#createDefaultRoot()
 */
protected void createDefaultRoot() {
	setRootEditPart(new SimpleRootEditPart());
}

private void disposeFont() {
	if (font != null) {
		font.dispose();
		font = null;
	}
}

/**
 * Indicates that the palette should scroll using a native vertical scrollbar as opposed
 * to individual lightweight buttons that appear dynamically on each drawer. The default
 * settings is <code>false</code>. Enabling this setting requires additional horizontal
 * screen space for the scrollbar. Therefore, its use is discouraged.
 * <p> 
 * This setting must be changed prior to calling 
 * {@link ScrollingGraphicalViewer#createControl(org.eclipse.swt.widgets.Composite)}. 
 * After the control is  created, changing this setting will have no effect.
 * </p>
 * 
 * @param	value	<code>true</code> if a vertical scrollbar should be displayed
 */
public void enableVerticalScrollbar(boolean value) {
	this.globalScrollbar = value;
}

/**
 * @return	the DrawerEditPart that has the given part; part, if part is a drawer; null, if
 * 			part is not in a drawer
 */
private DrawerEditPart findContainingDrawer(EditPart part) {
	if (part == null)
		return null;
	if (part instanceof DrawerEditPart)
		return (DrawerEditPart)part;
	return findContainingDrawer(part.getParent());
}

/**
 * Notifies registered listeners of change in the active tool on the palette
 */
protected void fireModeChanged() {
	if (paletteListeners == null)
		return;
	for (int listener = 0; listener < paletteListeners.size(); listener++)
		((PaletteListener) paletteListeners.get(listener))
			.activeToolChanged(this, activeEntry);
}

/**
 * @return the customizer
 */
public PaletteCustomizer getCustomizer() {
	return customizer;
}

/**
 * NOTE: A PaletteCustomizer must be set for this viewer using the 
 * {@link #setCustomizer(PaletteCustomizer)} method before this method is invoked.
 * 
 * @return The dialog that can be used to customize entries on the palette
 */
public PaletteCustomizerDialog getCustomizerDialog() {
	if (customizerDialog == null) {
		customizerDialog = new PaletteCustomizerDialog(
				getControl().getShell(), getCustomizer(), getPaletteRoot());
	}
	return customizerDialog;
}

/**
 * @return the entry for the currently active tool
 */
public ToolEntry getActiveTool() {
	return activeEntry;
}

/**
 * Returns the palette's root model.
 * @return the palette root
 */
public PaletteRoot getPaletteRoot() {
	return paletteRoot;
}

/**
 * @return	The PaletteViewerPreferences that this palette is using to store its
 * 			preferences (if none has been set, it returns the default one, which uses
 * 			the GEF preference store)
 */
public PaletteViewerPreferences getPaletteViewerPreferences() {
	return prefs;
}

private ToolEntryEditPart getToolEntryEditPart(ToolEntry entry) {
	return (ToolEntryEditPart)getEditPartRegistry().get(entry);
}

/**
 * @see com.architexa.org.eclipse.gef.ui.parts.GraphicalViewerImpl#handleDispose(
 *      	org.eclipse.swt.events.DisposeEvent)
 */
protected void handleDispose(DisposeEvent e) {
	super.handleDispose(e);
	disposeFont();
}

/**
 * @see com.architexa.org.eclipse.gef.ui.parts.GraphicalViewerImpl#handleFocusGained(FocusEvent)
 */
protected void handleFocusGained(FocusEvent fe) {
	super.handleFocusGained(fe);
	/*
	 * Fix for Bug# 63297
	 * When the palette gets focus, the LWS will take care of setting focus on the
	 * correct figure.  However, when the user clicks on an entry, the focus is "thrown
	 * away."  In that case, the LWS would set focus on the first focusable figure.  We
	 * override that here and set focus on the correct/selected figure.  Invoking
	 * getFocusEditPart().setFocus(true) does not work because that editpart thinks it
	 * already has focus (it's not aware of focus being thrown away) and does nothing.
	 */
	if (focusPart == null) {
		IFigure fig = ((GraphicalEditPart)getFocusEditPart()).getFigure();
		fig.internalGetEventDispatcher().requestFocus(fig);
	}
}

/**
 * @see com.architexa.org.eclipse.gef.ui.parts.GraphicalViewerImpl#hookControl()
 */
protected void hookControl() {
	super.hookControl();
	FigureCanvas canvas = getFigureCanvas();
	canvas.getViewport().setContentsTracksWidth(true);
	canvas.getViewport().setContentsTracksHeight(!globalScrollbar);
	canvas.setHorizontalScrollBarVisibility(FigureCanvas.NEVER);
	canvas.setVerticalScrollBarVisibility(
		globalScrollbar ? FigureCanvas.ALWAYS : FigureCanvas.AUTOMATIC);
	if (prefs != null)
		prefs.addPropertyChangeListener(prefListener);
	updateFont();
}

/**
 * Returns true if the given PaletteDrawer is expanded
 * @param drawer the PaletteDrawer
 * @return true if expanded
 */
public boolean isExpanded(PaletteDrawer drawer) {
	EditPart ep = (EditPart)getEditPartRegistry().get(drawer);
	if (ep instanceof DrawerEditPart)
		return ((DrawerEditPart)ep).isExpanded();
	return false;
}

/**
 * Returns true if the given PaletteDrawer is pinned
 * @param drawer the PaletteDrawer
 * @return true if pinned
 */
public boolean isPinned(PaletteDrawer drawer) {
	EditPart ep = (EditPart)getEditPartRegistry().get(drawer);
	if (ep instanceof DrawerEditPart)
		return ((DrawerEditPart)ep).isPinnedOpen();
	return false;
}

/**
 * The given PaletteListener will not be notified of active tool changes in the palette.
 * 
 * @param	paletteListener		the PaletteListener which doesn't want to be notified of 
 * 								active tool changes in the palette anymore
 */
public void removePaletteListener(PaletteListener paletteListener) {
	paletteListeners.remove(paletteListener);
}

/**
 * Tries to apply the state of the given IMemento to the contents of this viewer.  It
 * fails silently, i.e. no exceptions are thrown if the given state could not be applied.
 * 
 * @param memento	The memento that has the state to be applied to the contents of this
 * 					viewer
 * @return	a boolean indicating whether or not the given memento was successfully applied
 * @since 3.0
 */
public boolean restoreState(IMemento memento) {
	try {
		PaletteEditPart part = (PaletteEditPart)getEditPartRegistry()
				.get(getPaletteRoot());
		if (part != null)
			part.restoreState(memento);
	} catch (RuntimeException re) {
		/*
		 * @TODO:Pratik   Perhaps you should log this exception.  Or not catch it at all. 
		 */
		return false;
	}
	return true;
}

/**
 * @see	ScrollingGraphicalViewer#reveal(EditPart)
 */
public void reveal(EditPart part) {
	// If the given part is a drawer, we don't need to expand it.  Hence, when invoking
	// findContainingDrawer(), we use part.getParent()
	DrawerEditPart drawer = findContainingDrawer(part.getParent());
	if (drawer != null && !drawer.isExpanded())
		drawer.setExpanded(true);
	// if the part is inside a stack, set it to be the top level item of the stack.
	if (part.getParent() != null && part.getParent() instanceof PaletteStackEditPart)
		((PaletteStack)part.getParent().getModel()).setActiveEntry((PaletteEntry)part.getModel());
	super.reveal(part);
}

/**
 * Captures the state of the contents of this viewer in the given memento
 * @param memento	the IMemento in which the state is to be saved
 * @since 3.0
 */
public void saveState(IMemento memento) {
	// Bug# 69026 - The PaletteRoot can be null initially for VEP
	PaletteEditPart base = (PaletteEditPart)getEditPartRegistry().get(getPaletteRoot());
	if (base != null)
		base.saveState(memento);
}

/**
 * Sets the customizer.
 * 
 * @param	customizer		the customizer to be set
 */
public void setCustomizer(PaletteCustomizer customizer) {
	this.customizer = customizer;
}

/**
 * Sets the active entry for this palette.  The Editpart for the given entry will be
 * activated (selected).
 * 
 * @param	newMode		the ToolEntry whose EditPart has to be set as the active tool 
 * 						in this palette
 */
public void setActiveTool(ToolEntry newMode) {
	if (newMode == null)
		newMode = getPaletteRoot().getDefaultEntry();
	if (activeEntry != null)
		getToolEntryEditPart(activeEntry).setToolSelected(false);
	activeEntry = newMode;
	if (activeEntry != null) {
		ToolEntryEditPart editpart = getToolEntryEditPart(activeEntry);
		editpart.setToolSelected(true);
	}
	fireModeChanged();
}

/**
 * Sets the root for this palette.
 * @param	root	the PaletteRoot for this palette
 */
public void setPaletteRoot(PaletteRoot root) {
	if (root == paletteRoot)
		return;
	paletteRoot = root;
	if (paletteRoot != null) {
		EditPart palette =
			getEditPartFactory().createEditPart(getRootEditPart(), root);
		getRootEditPart().setContents(palette);
	}
}

/**
 * This palette will use the given PaletteViewerPreferences to store all its preferences.
 * <p>
 * NOTE: This method should be invoked by a client only once (before the first time 
 * {@link #getPaletteViewerPreferences()} is invoked).  Trying to invoke this method
 * after that could lead to problems where some preferences would still be stored in the
 * old preference store.
 * </p>
 * 
 * @param	prefs	the PaletteViewerPreferences that is to be used to store all the
 * 					preferences for this palette
 */
public void setPaletteViewerPreferences(PaletteViewerPreferences prefs) {
	if (this.prefs != null)
		this.prefs.removePropertyChangeListener(prefListener);
	this.prefs = prefs;
	if (getControl() != null && !getControl().isDisposed())
		this.prefs.addPropertyChangeListener(prefListener);
}

/**
 * @see com.architexa.org.eclipse.gef.ui.parts.AbstractEditPartViewer#unhookControl()
 */
protected void unhookControl() {
	super.unhookControl();
	disposeFont();
	if (prefs != null)
		prefs.removePropertyChangeListener(prefListener);
}

private void updateFont() {
	disposeFont();

	if (getControl() == null || getControl().isDisposed())
		return;
	
	font = new Font(Display.getCurrent(), getPaletteViewerPreferences().getFontData());
	getControl().setFont(font);
	getFigureCanvas().getViewport().invalidateTree();
	getFigureCanvas().getViewport().revalidate();
	getFigureCanvas().redraw();
}

}
